package org.apache.rocketmq.tools.admin;

import com.google.common.base.Strings;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.srvutil.ServerUtil;
import org.apache.rocketmq.tools.command.SubCommand;
import org.apache.rocketmq.tools.topic.DeleteTopicSubCommand;
import org.apache.rocketmq.tools.topic.UpdateTopicSubCommand;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MQAdminStartup {
    //存储所有命令对象的集合
    protected static final List<SubCommand> SUB_COMMANDS = new ArrayList<>();

    //RocketMQ主目录
    private static final String ROCKETMQ_HOME = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
            System.getenv(MixAll.ROCKETMQ_HOME_ENV));


    public static void main(String[] args) {
        //updateTopic -c DefaultCluster -n 127.0.0.1:9876 -t TestTopic
        System.out.println(Arrays.asList(args));
        main0(args, null);
    }
    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2025/1/14
     * @方法描述：启动控制台程序的方法
     */
    public static void main0(String[] args, RPCHook rpcHook) {
        //设置协议版本号
        System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
        //初始化所有命令对象，初始化完毕的命令对象都会存放到SUB_COMMANDS集合中
        initCommand();
        try {
            //接下来开始解析命令行参数
            switch (args.length) {
                //用户没有设置命令行参数时，打印帮助信息
                case 0:
                    printHelp();
                    break;
                //如果设置了两个命令行参数
                case 2://判断第一个参数是否为help命令
                    //如果是help参数，那么这个命令很可能就是要查看具体命令的帮助信息
                    if (args[0].equals("help")) {
                        //第二个参数的就是具体的命令对象的名称，这个时候所有的命令对象都初始化完毕了
                        //这里就可以根据第二个参数的名称找到具体的命令对象
                        SubCommand cmd = findSubCommand(args[1]);
                        if (cmd != null) {
                            //创建封装命令行参数的对象
                            Options options = ServerUtil.buildCommandlineOptions(new Options());
                            //把命令行参数封装到options对象中
                            options = cmd.buildCommandlineOptions(options);
                            if (options != null) {
                                //打印命令行帮助信息到控制台
                                ServerUtil.printCommandLineHelp("mqadmin " + cmd.commandName(), options);
                            }
                        } else {
                            //命令不存在则在控制台输出提示信息
                            System.out.printf("The sub command %s not exist.%n", args[1]);
                        }
                        break;
                    }
                    //以下则是处理只有一个命令行参数或者多个命令行参数的情况
                case 1:
                default:
                    //还是先获得第一个命令行参数，这个参数就是具体的命令对象的名称，我在第六版本代码的测试类中设置的命令行参数如下
                    //updatetopic -n localhost:9876 -t TestTopic -c DefaultCluster
                    //第一个参数就是要使用的命令对象的名称，第二个-n参数是NameServer的地址，-t参数是要创建的Topic的名称，-c参数是Broker集群的的名称
                    //得到了要使用的命令对象的名称后，就根据这个名称寻找到对用的命令对象
                    //在测试类中，我设置的命令对象的名称是updatetopic，所以这里就会找到UpdateTopicSubCommand对象，之后要对Broker集群执行创建主题的操作
                    SubCommand cmd = findSubCommand(args[0]);
                    if (cmd != null) {
                        //得到命令行中剩余的命令数组
                        String[] subargs = parseSubArgs(args);
                        //创建封装命令行参数的对象
                        Options options = ServerUtil.buildCommandlineOptions(new Options());
                        //把命令行参数封装到options对象中，然后把options对象中的参数信息封装到CommandLine对象中
                        final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new DefaultParser());
                        if (null == commandLine) {
                            return;
                        }
                        //对Namesrv地址进行设置
                        if (commandLine.hasOption('n')) {
                            String namesrvAddr = commandLine.getOptionValue('n');
                            //把命令行中的Namesrv地址设置到系统属性中，这样一来，程序就可以通过System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY)随时获取到Namesrv的地址了
                            System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, namesrvAddr);
                        }
                        //接下来就可以使用命令对象执行具体的命令了
                        if (rpcHook != null) {
                            cmd.execute(commandLine, options, rpcHook);
                        }
                        else {
                            //因为我们并没有设置rpcHook，所以程序会执行到这个分支
                            cmd.execute(commandLine, options,null);
                        }
                    } else {
                        //程序执行到这里就意味着没有找到对应的命令对象，这个时候就在控制台输出提示信息
                        System.out.printf("The sub command %s not exist.%n", args[0]);
                    }
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //初始化命令对象的方法
    public static void initCommand() {
        //初始化更新或者创建主题信息的命令对象
        initCommand(new UpdateTopicSubCommand());
        //初始化删除主题信息的命令对象
        initCommand(new DeleteTopicSubCommand());
    }

    //打印帮助信息到控制台的方法
    private static void printHelp() {
        //打印帮助信息开头部分
        System.out.printf("The most commonly used mqadmin commands are:%n");
        //遍历所有的命令对象，打印命令对象的名称和描述信息
        for (SubCommand cmd : SUB_COMMANDS) {
            System.out.printf("   %-35s %s%n", cmd.commandName(), cmd.commandDesc());
        }//打印帮助信息结尾部分
        System.out.printf("%nSee 'mqadmin help <command>' for more information on a specific command.%n");
    }
    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @date:2025/1/15
     * @方法描述：根据名称查找命令对象的方法
     */
    private static SubCommand findSubCommand(final String name) {
        for (SubCommand cmd : SUB_COMMANDS) {
            //如果命令对象的名称和传入的名称相同，或者命令对象的别名和传入的名称相同，那么就返回这个命令对象
            if (cmd.commandName().equalsIgnoreCase(name) || cmd.commandAlias() != null && cmd.commandAlias().equalsIgnoreCase(name)) {
                return cmd;
            }
        }
        return null;
    }

    //解析命令行参数，把命令行剩余参数信息封装到数组的方法
    private static String[] parseSubArgs(String[] args) {
        if (args.length > 1) {
            String[] result = new String[args.length - 1];
            for (int i = 0; i < args.length - 1; i++) {
                result[i] = args[i + 1];
            }
            return result;
        }
        return null;
    }

    //初始化命令对象的方法
    public static void initCommand(SubCommand command) {
        SUB_COMMANDS.add(command);
    }
}
